#include "tests.hpp"

#include "TestFunctionDefine.hpp"
#include "common/camera.hpp"
#include "common/noiseUntil.hpp"
#include "math/math_operation.hpp"
#include "math/random_generator.hpp"
#include "math/tmatrix.hpp"
#include "sample/sampler.hpp"
#include "shape/plane.hpp"
#include "shape/rectangle.hpp"
#include "shape/sphere.hpp"
#include "shape/triangle.hpp"

namespace Tests {

#pragma region Test Function Array

TestFuncType tests[] = { SimpleCameraTest, CleanResource };

// Global state
const ui32 ImageW = 500;
const ui32 ImageH = 500;

#pragma endregion

#pragma region Ray Trace Scene Setup

// TODO: A template scene setup for test case use;

#define SETUP_SCENE( name, ... ) \
  std::vector<Shape *> scene_##name = { __VA_ARGS__ }; // namespace Tests

#define GET_SCENE( name ) scene_##name

#define DESTROY_SCENE_DEF( name )                                  \
  void destroy_scene_##name() {                                    \
    for ( typename decltype( GET_SCENE( name ) )::value_type val : \
          GET_SCENE( name ) ) {                                    \
      if ( val != nullptr ) {                                      \
        delete val;                                                \
        val = nullptr;                                             \
      }                                                            \
    }                                                              \
    GET_SCENE( name ).clear();                                     \
  }

#define SCENE_INIT( name, ... )    \
  SETUP_SCENE( name, __VA_ARGS__ ) \
  DESTROY_SCENE_DEF( name )

SCENE_INIT( 2dCamera,
            new Sphere( vec3f( 250, 250, -620 ), 150, vec3f( 0, 0, 1 ) ),
            new Triangle( vec3f( 300, 600, -800 ), vec3f( 0, 100, -1000 ),
                          vec3f( 450, 20, -1000 ), vec3f( 1, 0, 0 ) ),
            new Rectangle( vec3f( 0, 0, 1.f ), vec3f( 250, 250, -300 ),
                           vec2f( 300.f, 200.f ) ) )

SCENE_INIT( 2dCamera1,
            new Sphere( vec3f( 250, 250, -620 ), 150, vec3f( 0, 0, 1 ) ),
            new Triangle( vec3f( 300, 600, -800 ), vec3f( 0, 100, -1000 ),
                          vec3f( 450, 20, -1000 ), vec3f( 1, 0, 0 ) ) )

SCENE_INIT( 3dCamera, new Sphere( vec3f( 0, 0, -200 ), 150, vec3f( 0, 0, 1 ) ) )

#define DESTROY_SCENE( name ) destroy_scene_##name();

void CleanResource() {
  DESTROY_SCENE( 2dCamera )
  DESTROY_SCENE( 2dCamera1 )
  DESTROY_SCENE( 3dCamera )
}

#undef SETUP_SCENE
#undef DESTROY_SCENE_DEF
#undef SCENE_INIT
#pragma endregion

#pragma region Base Test Case

void TestVector() { vec3f a = 3.f * vec3f( 0.1 ); }

#pragma endregion

#pragma region UV Coordinate Test

void SimpleUVTest() {

  TEST_CASE<ImageW, ImageH>( "SimpleUV",
                             [&]( ui32 x, ui32 y, ui32 w, ui32 h ) -> vec4f {
                               vec4f color( 0, 0, 0, 1 );
                               color.r = f32( x ) / w;
                               color.g = f32( y ) / h;
                               return color;
                             } );
}

#pragma endregion

#pragma region Single Sample Per Pixel Test

void SimpleRayCasterTest() {

  auto num = GET_SCENE( 2dCamera ).size();
  f32 tmax = 2000.f;
  HitRecord rec;
  bool is_a_hit = false;

  Shape **shapes = GET_SCENE( 2dCamera ).data();

  TEST_CASE<ImageW, ImageW>(
      "SimpleRayCaster", [&]( ui32 x, ui32 y, ui32 w, ui32 h ) -> vec4f {
        vec4f color( 0, 0, 0, 1 );
        tmax = 2000.f;
        is_a_hit = false;

        Ray r( vec3f( x - .5f, y - .5f, 0 ), vec3f( 0, 0, -1 ) );
        r.tmax = tmax;

        // TODO: optimize
        for ( ui64 i = 0; i < num; i++ ) {
          if ( shapes[i]->hit( r, 1, tmax, 0, rec ) ) {
            tmax = rec.t;
            is_a_hit = true;
          }
        }
        if ( is_a_hit ) {
          color.r = rec.color.r;
          color.g = rec.color.g;
          color.b = rec.color.b;
        }
        return color;
      } );
}

#pragma endregion

#pragma region Gaussion Distribution Test

void GaussionProfileTest() {
  /**
   * @brief  Gaussion function for calculating this normal distribution
   * @note
   * @param  x:
   * @retval
   */
  constexpr auto gaussionFunc = []( ui32 x ) -> f64 {
    f64 delta = 60.f;
    f64 mean = 0;
    f64 a = ONE_DIVIDE_SQ_PI * delta;
    f64 b = -( (f64)x - mean ) * ( (f64)x - mean ) / ( 2 * delta * delta );
    return a * exp( b );
  };

  std::array<f32, ImageW / 2 + 1> gaussionValue{ 0.0f };
  for ( ui32 i = 0; i < ImageW / 2; i++ ) {
    gaussionValue[i] = static_cast<f32>( gaussionFunc( i ) );
  }

  decltype( gaussionValue ) normalizedGaussion;
  normalizedGaussion.fill( 0 );

  f32 t = 1.0f;
  normalizeValueInterval( gaussionValue, normalizedGaussion, t );

  TEST_CASE<ImageW, ImageH>(
      "GaussionProfile",
      // TODO: implementation of the kernel
      [&]( ui32 x, ui32 y, ui32 w, ui32 h ) -> vec4f {
        vec3f color( 0 );
        if ( x < ImageW / 2 ) {
          color = vec3f( normalizedGaussion[ImageW / 2 - x - 1] );
        } else
          color = vec3f( normalizedGaussion[x - ImageW / 2] );

        if ( y < ImageH / 2 )
          color *= normalizedGaussion[ImageH / 2 - y - 1];
        else
          color *= normalizedGaussion[y - ImageH / 2];
        //  color.mulCompWise( vec3f( 0.8f, 0.1f, 0.0f ) );
        return vec4f( color, 1.0f );
      }

  );
}

constexpr auto HighFrequencySignal = []( float x, float y ) -> float {
  return ( 1 + sinf( ( x * x + y * y ) * .001f ) ) * .5f;
};

// Sample Pixel Center
void HighFrequencySignalPointSampleTest() {

  TEST_CASE<ImageW, ImageH>(

      "HightFrequencySignalPointSample",
      [&]( ui32 x, ui32 y, ui32 w, ui32 h ) -> vec4f {
        f32 u = x - .5f;
        f32 v = y - .5f;

        u *= 5.f;
        v *= 5.f;
        vec3f color( 0.f );
        color = vec3f( HighFrequencySignal( u, v ) );
        return vec4f( color, 1.f );
      } );
}

// Sample the pixel area averaging all color
void HightFrequencySignalBoxSampleTest() {
  // TODO: Implement box filtering sampling process

  TEST_CASE<ImageW, ImageH>(

      "HightFrequencySignalBoxSample",
      [&]( ui32 x, ui32 y, ui32 w, ui32 h ) -> vec4f {
        f32 u = static_cast<f32>( x );
        f32 v = static_cast<f32>( y );
        vec2f samplePoint[9] = { vec2f( u - 1.f, v + 1.f ),
                                 vec2f( u, v + 1.f ),
                                 vec2f( u + 1.f, v + 1.f ),
                                 vec2f( u - 1.f, v ),
                                 vec2f( u, v ),
                                 vec2f( u + 1.f, v ),
                                 vec2f( u - 1.f, v - 1.f ),
                                 vec2f( u, v - 1.f ),
                                 vec2f( u + 1.f, v - 1.f ) };
        SampleAndFilteringFunc::boxFilter( samplePoint, 9 );
        vec3f color( 0.f );
        for ( ui8 i = 0; i < 9; i++ ) {
          samplePoint[i] *= 5.f;
          vec2f uv = samplePoint[i];
          color += vec3f( HighFrequencySignal( uv.x, uv.y ) );
        }
        color /= 9;

        return vec4f( color, 1.f );
      } );
}

#pragma endregion

#pragma region Randomly Multi - Sample Per Pixel Test

void MultiSamplePerPixel( const char *name, int samplePerPixel ) {

  Shape **shapes = GET_SCENE( 2dCamera1 ).data();

  auto num = GET_SCENE( 2dCamera1 ).size();
  f32 tmax = 2000.f;
  HitRecord rec;
  bool is_a_hit = false;

  // Assume not greater than 100 sample point
  vec2f samplePointArray[100];

  TEST_CASE<ImageW, ImageW>(
      name, [&]( ui32 x, ui32 y, ui32 w, ui32 h ) -> vec4f {
        vec3f color;

        SampleAndFilteringFunc::random( samplePointArray, samplePerPixel );
        for ( int s = 0; s < samplePerPixel; s++ ) {
          vec2f offset = samplePointArray[s];
          Ray r( vec3f( x + offset.x, y + offset.y, 0 ), vec3f( 0, 0, -1 ) );
          tmax = 2000.f;
          is_a_hit = false;
          for ( ui8 i = 0; i < num; i++ ) {
            if ( shapes[i]->hit( r, 1, tmax, 0, rec ) ) {
              tmax = rec.t;
              is_a_hit = true;
            }
          }
          if ( is_a_hit ) {
            color.r += rec.color.r;
            color.g += rec.color.g;
            color.b += rec.color.b;
          }
        }
        color = color / samplePerPixel;
        return vec4f( color, 1.0 );
      } );
}

void MultiSamplePerPixelX32() {
  MultiSamplePerPixel( "MultiSamplePerPixel_x32", 32 );
}
void MultiSamplePerPixelX16() {
  MultiSamplePerPixel( "MultiSamplePerPixel_x16", 16 );
}
void MultiSamplePerPixelX8() {
  MultiSamplePerPixel( "MultiSamplePerPixel_x8", 8 );
}
void MultiSamplePerPixelX4() {
  MultiSamplePerPixel( "MultiSamplePerPixel_x4", 4 );
}

#pragma endregion

#pragma region Grid Sample

void GridSamplePerPixel( const char *name, ui32 gridSize ) {

  Shape **shapes = GET_SCENE( 2dCamera1 ).data();

  const ui64 num = GET_SCENE( 2dCamera1 ).size();
  f32 tmax = 2000.f;
  HitRecord rec;
  bool is_a_hit = false;

  vec2f samplePointArray[50];

  TEST_CASE<ImageW, ImageW>(
      name,
      [&]( ui32 x, ui32 y, ui32 w, ui32 h ) -> vec4f {
        vec3f color;

        SampleAndFilteringFunc::regularGridSample( samplePointArray, gridSize );
        ui32 sqrt_samples = ui32( sqrtf( (f32)gridSize ) );
        for ( ui32 s = 0; s < gridSize; s++ ) {
          vec2f offset = samplePointArray[s];
          tmax = 2000.f;
          is_a_hit = false;
          Ray r( vec3f( x + offset.x, y + offset.y, 0 ), vec3f( 0, 0, -1 ) );
          for ( ui64 i = 0; i < num; i++ ) {
            if ( shapes[i]->hit( r, 1, tmax, 0, rec ) ) {
              tmax = rec.t;
              is_a_hit = true;
            }
          }
          if ( is_a_hit ) {
            color.r += rec.color.r;
            color.g += rec.color.g;
            color.b += rec.color.b;
          }
        }
        color = color / f32( gridSize );
        return vec4f( color, 1.0 );
      }

  );
}

void GridSample_x4() { GridSamplePerPixel( "GridSample_x4", 4 ); }

void GridSample_x8() { GridSamplePerPixel( "GridSample_x8", 8 ); }

#pragma endregion

#pragma region N - Rooks Sample Per Pixel

void NRooksSamplePerPixel( const char *name, ui32 num_samples ) {
  // TODO: nRooks Sample Technique
}

void NRooksSamplePerPixelx4() {
  NRooksSamplePerPixel( "NRooksSamplePerPixel_x4", 4 );
}

void NRooksSamplePerPixelx8() {
  NRooksSamplePerPixel( "NRooksSamplePerPixel_x8", 8 );
}

#pragma endregion

#pragma region Jittled MultiSample Per Pixel

void JitteredSamplePerPixel() {
  // TODO: Jittered Sample
}

void MultiJitteredSamplePerPixel() {
  // TODO: MultiJittered Sample Technique
}

#pragma endregion

#pragma region CameraTest

void SimpleCameraTest() {
  /* TODO: axis aligned camera test */
  AxisAlignedCamera<ImageW, ImageW> camera( 50.f, vec3f( 0.0f, 0.5f, 0.1f ),
                                            vec3f( 0, 0, -100 ) );

  auto num = GET_SCENE( 3dCamera ).size();
  f32 tmax = 2000.f;
  HitRecord rec;
  bool is_a_hit = false;

  Shape **shapes = GET_SCENE( 3dCamera ).data();

  vec3f light( 0, 1, 0 );

  TEST_CASE<ImageW, ImageW>(
      "3DCameraTest", [&]( ui32 x, ui32 y, ui32 w, ui32 h ) -> vec4f {
        vec4f color( 0.5, 0.5, 0.2, 1 );
        tmax = 2000.f;
        is_a_hit = false;

        Ray r = camera.ray(
            vec2f( ( x - .5f ) / f32( ImageW ), ( y - .5f ) / f32( ImageH ) ) );

        // TODO: optimize
        for ( ui64 i = 0; i < num; i++ ) {
          if ( shapes[i]->hit( r, 1, tmax, 0, rec ) ) {
            tmax = rec.t;
            is_a_hit = true;
          }
        }
        if ( is_a_hit ) {
          color = std::max( dot( rec.normal, camera.forward() ), 0.0f ) *
                  ( rec.normal * 0.5 + 0.5 );
          color.a = 1.0f;
        }
        return color;
      } );
}

void ThinLensCameraTest() {
  // TODO: Thin Lens Camera Test
  ThinLensCamera<ImageW, ImageW> camera( 10.f, 200.f, vec3f( 0 ),
                                         vec3f( 100, 100, -200 ) );
}

#pragma endregion

#pragma region Perlin noise test

void PerlinNoiseTest() {

  NoiseUntil noise;

  TEST_CASE<ImageW, ImageH>(
      "PerlinNoiseTest", [&]( ui32 x, ui32 y, ui32 w, ui32 h ) -> vec4f {
        vec3f color;
        vec2f uv( ( x - .5f ) / f32( ImageW ), ( y - .5f ) / f32( ImageH ) );
        uv *= 5.f;
        color = noise.perlinNoise( vec3f( uv, 1.0f ) );

        return vec4f( color, 1.0f );
      } );
}

#pragma endregion

} // namespace Tests